
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;

public class MazeClient {
    private static Socket sock = null;
    private String serverHostname = null;
    private int serverPort = 0;
    private InputStream sockInput = null;
    private OutputStream sockOutput = null;
    private final LinkedList<Integer> serverPortPool = new LinkedList<Integer>();
    private String backupPort;
    private String backupIP;
    private AtomicBoolean localClientExists;
    private Game game;
    private final PeerMazeServer server;
    int playerId;
    private Player player;

    public static void main(String argv[]) throws Exception {
        String hostname = "localhost";
        // TODO: make the port dynamic for multiple clients
        int port = 9000;

        //MazeClient client = new MazeClient(hostname, port);

        //client.sendSomeMessages();

        // run the heartbeat monitor every 1 second and notify if couldn't
        // connect.

    }

    /**
     * @param serverHostname
     * @param serverPort
     * @param server
     */
    public MazeClient(String serverHostname, int serverPort, PeerMazeServer server, Player player) {
        this.serverHostname = serverHostname;
        this.serverPort = serverPort;
        this.server = server;
        this.player = player;
    }

    public void sendSomeMessages() throws Exception {
        System.err.println("Opening connection to " + serverHostname + " port " //$NON-NLS-1$
                + this.serverPort);
        boolean alreadyJoined = false;
        while (true) {
            byte[] buf = new byte[4096];

            String command = ""; //$NON-NLS-1$
            if (!alreadyJoined && server.getGame().getStatus().status != StatusEnum.GAME_STARTED) {
                command = new Scanner(System.in).nextLine();
                if (command.equalsIgnoreCase("joingame")) {
                    alreadyJoined = setupConnectionWithPrimary(buf, true);
                }
            }
            try {
                for (Player player : server.getGame().getPlayerList()) {
                    if (!alreadyJoined &&
                            player.getId() == playerId
                            && player.getServerType().equalsIgnoreCase("Primary")) {
                        alreadyJoined = setupNewSocket(buf, false);
                        break;
                    }
                }

                command = ""; //$NON-NLS-1$
                while (command.trim().length() == 0)
                    command = new Scanner(System.in).nextLine().trim();

                if (this.sockOutput != null
                        && !command
                        .equalsIgnoreCase("joingame")) {
                    this.sockOutput.write(command.getBytes(), 0,
                            command.getBytes().length);
                    this.sockOutput.flush();
                }
                buf = new byte[4096];
                if (this.sockInput != null) {
                    this.sockInput.read(buf, 0, buf.length);
                }
            } catch (Exception serverException) {
                //consider creating another socket for this player
                setupNewSocket(buf, false);
                //start a new backup server and start the corresponding thread.

            }


            System.out.println(new String(buf));
        }
    }

    public boolean setupNewSocket(byte[] buf, boolean receiveWelcomeMessage) throws Exception {
        boolean alreadyJoined;//Incase if the primary server is not responding, connect to Secondary.
        System.out.println("Primary down, connecting to backup... " + backupIP + ":" + backupPort);
        serverHostname = backupIP;
        serverPort = Integer.parseInt(backupPort.split("\n")[0]);
        if(!receiveWelcomeMessage){
            serverPort++;
        }
        alreadyJoined = setupConnectionWithPrimary(buf, receiveWelcomeMessage);
        System.out.println("Connected to the Backup Server successfully!!!... " + backupIP + ":" + backupPort);
        return alreadyJoined;
    }

    private boolean setupConnectionWithPrimary(byte[] buf, boolean receiveWelcomeMessage) throws Exception {
        boolean alreadyJoined = false;
        sock = new Socket(this.serverHostname, this.serverPort);

        sock.setTcpNoDelay(true);
        this.sockInput = new BufferedInputStream(sock
                .getInputStream());
        this.sockOutput = sock.getOutputStream();
        sock.setTcpNoDelay(true);
        sock.setKeepAlive(true);
        if(!server.localServerSocketListening){
            //new Timer().schedule(new HeartBeatTask(sock, this, server), 1000, 1000);
        }

        //sock.setSoTimeout(1000);
        // Execute this branch only once during client setup
        String temp = "";
        String response = "";
        if (receiveWelcomeMessage) {
            this.sockInput.read(buf, 0, buf.length);
            System.out.println(new String(buf));
            // Game begins after 20s
            response = new String(buf).trim();
            String welcomeMessage = response.split("\n")[0];
            playerId = Integer.parseInt(welcomeMessage.substring(welcomeMessage.indexOf("Welcome") + 15));
            alreadyJoined = true;
            if (response.contains("Backup=")) {
                temp = response.substring(response.indexOf("Backup=") + 7);
                if (temp.length() > 0) {
                    backupPort = temp.split(":")[1];
                    backupIP = temp.split(":")[0];
                    System.out.println("Backup server : " + temp);
                }
            }
        }
        if (!receiveWelcomeMessage) {
            System.out.println("Waiting for PrimaryDown from Server...");
            this.sockInput.read(buf, 0, buf.length);
            System.out.println("Value from 2nd Primary server is got as ..." + new String(buf).trim());
        }
        System.out.println("Backup Port : " + backupPort);
        if ((backupPort.contains("null") || new String(buf).contains("PrimaryDown")) && notPrimaryServer()) {

            if (backupPort.startsWith("null"))
                backupPort = "9000";
            ServerSocket serverSocket = tryBackupServer();
            backupIP = serverSocket.getInetAddress().getCanonicalHostName();
            backupPort = "" + serverSocket.getLocalPort();
            // Send a grid request with the port
            String gridReq = "GridRequest:" + serverSocket.getInetAddress().getCanonicalHostName() + "=" + serverSocket.getLocalPort();
            this.sockOutput.write(gridReq.getBytes(), 0, gridReq.getBytes().length);
            sockOutput.flush();
            game = (Game) new ObjectInputStream(sockInput).readObject();
            System.out.println("The GRID response is : \n" + game.getGrid().toString());
            //now we should start a parallel server here.

            server.setBackupServerSocket(serverSocket);
        }

        return alreadyJoined;
    }

    private boolean moreThanOneActivePlayer() {
        int count = 0;
        if (server.getGame() != null && server.getGame().getPlayerList() != null) {
            for (Player player : server.getGame().getPlayerList()) {
                if (player.getStatus() == StatusEnum.ACTIVE) {
                    count++;
                }
            }
        }

        return count > 1;
    }

    ServerSocket tryBackupServer() {
        // Create the backup server instance
        ServerSocket backupServer = null;
        System.out.println("Trying to bring up backup Port : " + backupPort);
        int startPort = Integer.parseInt(backupPort.split("\n")[0]);
        boolean serverAvailable = false;
        while (!serverAvailable) {
            try {
                backupServer = new ServerSocket(startPort++);
                serverAvailable = true;
            } catch (Exception e) {
                //cannot start at this port on the same machine.
                //find the next vacant port plz.
                continue;
            }
        }
        return backupServer;
    }


    private boolean notPrimaryServer() {
        return player.getServerType()==null || !player.getServerType().equalsIgnoreCase("Primary");
    }

    private boolean notFirstPlayer(String temp) {
        return !temp.contains("Player 1");
    }

    public void setSocket() throws Exception {
        byte[] buf = new byte[1024];
        setupNewSocket(buf, false);
    }
}